<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
    />
    <meta
      name="description"
      content="Render voxel primitives with different shape types."
    />
    <meta name="cesium-sandcastle-labels" content="Showcases, Voxels" />
    <title>Cesium Demo</title>
    <script type="text/javascript" src="../Sandcastle-header.js"></script>
    <script
      type="text/javascript"
      src="../../../Build/CesiumUnminified/Cesium.js"
      nomodule
    ></script>
    <script type="module" src="../load-cesium-es6.js"></script>
  </head>
  <body class="sandcastle-loading" data-sandcastle-bucket="bucket-requirejs.html">
    <style>
      @import url(../templates/bucket.css);
    </style>
    <div id="cesiumContainer" class="fullSize"></div>
    <div id="loadingOverlay"><h1>Loading...</h1></div>
    <div id="toolbar"></div>
    <script id="cesium_sandcastle_script">
      window.startup = async function (Cesium) {
        "use strict";
        //Sandcastle_Begin
        const viewer = new Cesium.Viewer("cesiumContainer", {
          baseLayer: Cesium.ImageryLayer.fromProviderAsync(
            Cesium.TileMapServiceImageryProvider.fromUrl(
              Cesium.buildModuleUrl("Assets/Textures/NaturalEarthII"),
            ),
          ),
          baseLayerPicker: false,
          geocoder: false,
          animation: false,
          timeline: false,
          projectionPicker: true,
          sceneModePicker: false,
        });

        viewer.extend(Cesium.viewerVoxelInspectorMixin);
        viewer.scene.debugShowFramesPerSecond = true;

        const globalTransform = Cesium.Matrix4.fromScale(
          Cesium.Cartesian3.fromElements(
            Cesium.Ellipsoid.WGS84.maximumRadius,
            Cesium.Ellipsoid.WGS84.maximumRadius,
            Cesium.Ellipsoid.WGS84.maximumRadius,
          ),
        );

        function ProceduralSingleTileVoxelProvider(shape) {
          this.shape = shape;
          this.minBounds = Cesium.VoxelShapeType.getMinBounds(shape).clone();
          this.maxBounds = Cesium.VoxelShapeType.getMaxBounds(shape).clone();
          this.dimensions = new Cesium.Cartesian3(8, 8, 8);
          this.names = ["color"];
          this.types = [Cesium.MetadataType.VEC4];
          this.componentTypes = [Cesium.MetadataComponentType.FLOAT32];
          this.globalTransform = globalTransform;
        }

        const scratchColor = new Cesium.Color();

        ProceduralSingleTileVoxelProvider.prototype.requestData = function (options) {
          if (options.tileLevel >= 1) {
            return Promise.reject(`No tiles available beyond level 0`);
          }

          const dimensions = this.dimensions;
          const voxelCount = dimensions.x * dimensions.y * dimensions.z;
          const type = this.types[0];
          const channelCount = Cesium.MetadataType.getComponentCount(type);
          const dataColor = new Float32Array(voxelCount * channelCount);

          const randomSeed = dimensions.y * dimensions.x + dimensions.x;
          Cesium.Math.setRandomNumberSeed(randomSeed);
          const hue = Cesium.Math.nextRandomNumber();

          for (let z = 0; z < dimensions.z; z++) {
            for (let y = 0; y < dimensions.y; y++) {
              const indexZY = z * dimensions.y * dimensions.x + y * dimensions.x;
              for (let x = 0; x < dimensions.x; x++) {
                const lerperX = x / (dimensions.x - 1);
                const lerperY = y / (dimensions.y - 1);
                const lerperZ = z / (dimensions.z - 1);

                const h = hue + lerperX * 0.5 - lerperY * 0.3 + lerperZ * 0.2;
                const s = 1.0 - lerperY * 0.2;
                const v = 0.5 + 2.0 * (lerperZ - 0.5) * 0.2;
                const color = Cesium.Color.fromHsl(h, s, v, 1.0, scratchColor);

                const index = (indexZY + x) * channelCount;
                dataColor[index + 0] = color.red;
                dataColor[index + 1] = color.green;
                dataColor[index + 2] = color.blue;
                dataColor[index + 3] = 0.75;
              }
            }
          }

          const content = Cesium.VoxelContent.fromMetadataArray([dataColor]);
          return Promise.resolve(content);
        };

        function ProceduralMultiTileVoxelProvider(shape) {
          this.shape = shape;
          this.minBounds = Cesium.VoxelShapeType.getMinBounds(shape).clone();
          this.maxBounds = Cesium.VoxelShapeType.getMaxBounds(shape).clone();
          this.dimensions = new Cesium.Cartesian3(4, 4, 4);
          this.paddingBefore = new Cesium.Cartesian3(1, 1, 1);
          this.paddingAfter = new Cesium.Cartesian3(1, 1, 1);
          this.names = ["color"];
          this.types = [Cesium.MetadataType.VEC4];
          this.componentTypes = [Cesium.MetadataComponentType.FLOAT32];
          this.globalTransform = globalTransform;

          this._levelCount = 2;
          this._allVoxelData = new Array(this._levelCount);

          const allVoxelData = this._allVoxelData;
          const channelCount = Cesium.MetadataType.getComponentCount(this.types[0]);
          const { dimensions } = this;

          for (let level = 0; level < this._levelCount; level++) {
            const dimAtLevel = Math.pow(2, level);
            const voxelCountX = dimensions.x * dimAtLevel;
            const voxelCountY = dimensions.y * dimAtLevel;
            const voxelCountZ = dimensions.z * dimAtLevel;
            const voxelsPerLevel = voxelCountX * voxelCountY * voxelCountZ;
            const levelData = (allVoxelData[level] = new Array(
              voxelsPerLevel * channelCount,
            ));

            for (let z = 0; z < voxelCountX; z++) {
              for (let y = 0; y < voxelCountY; y++) {
                const indexZY = z * voxelCountY * voxelCountX + y * voxelCountX;
                for (let x = 0; x < voxelCountZ; x++) {
                  const index = (indexZY + x) * channelCount;
                  levelData[index + 0] = x / (voxelCountX - 1);
                  levelData[index + 1] = y / (voxelCountY - 1);
                  levelData[index + 2] = z / (voxelCountZ - 1);
                  levelData[index + 3] = 0.5;
                }
              }
            }
          }
        }

        ProceduralMultiTileVoxelProvider.prototype.requestData = function (options) {
          const { tileLevel, tileX, tileY, tileZ } = options;

          if (tileLevel >= this._levelCount) {
            return Promise.reject(
              `No tiles available beyond level ${this._levelCount - 1}`,
            );
          }

          const type = this.types[0];
          const channelCount = Cesium.MetadataType.getComponentCount(type);
          const { dimensions, paddingBefore, paddingAfter } = this;
          const paddedDimensions = Cesium.Cartesian3.fromElements(
            dimensions.x + paddingBefore.x + paddingAfter.x,
            dimensions.y + paddingBefore.y + paddingAfter.y,
            dimensions.z + paddingBefore.z + paddingAfter.z,
          );
          const dimAtLevel = Math.pow(2, tileLevel);
          const dimensionsGlobal = Cesium.Cartesian3.fromElements(
            dimensions.x * dimAtLevel,
            dimensions.y * dimAtLevel,
            dimensions.z * dimAtLevel,
          );
          const minimumGlobalCoord = Cesium.Cartesian3.ZERO;
          const maximumGlobalCoord = new Cesium.Cartesian3(
            dimensionsGlobal.x - 1,
            dimensionsGlobal.y - 1,
            dimensionsGlobal.z - 1,
          );
          let coordGlobal = new Cesium.Cartesian3();

          const dataGlobal = this._allVoxelData;
          const dataTile = new Float32Array(
            paddedDimensions.x * paddedDimensions.y * paddedDimensions.z * channelCount,
          );

          for (let z = 0; z < paddedDimensions.z; z++) {
            const indexZ = z * paddedDimensions.y * paddedDimensions.x;
            for (let y = 0; y < paddedDimensions.y; y++) {
              const indexZY = indexZ + y * paddedDimensions.x;
              for (let x = 0; x < paddedDimensions.x; x++) {
                const indexTile = indexZY + x;

                coordGlobal = Cesium.Cartesian3.clamp(
                  Cesium.Cartesian3.fromElements(
                    tileX * dimensions.x + (x - paddingBefore.x),
                    tileY * dimensions.y + (y - paddingBefore.y),
                    tileZ * dimensions.z + (z - paddingBefore.z),
                    coordGlobal,
                  ),
                  minimumGlobalCoord,
                  maximumGlobalCoord,
                  coordGlobal,
                );

                const indexGlobal =
                  coordGlobal.z * dimensionsGlobal.y * dimensionsGlobal.x +
                  coordGlobal.y * dimensionsGlobal.x +
                  coordGlobal.x;

                for (let c = 0; c < channelCount; c++) {
                  dataTile[indexTile * channelCount + c] =
                    dataGlobal[tileLevel][indexGlobal * channelCount + c];
                }
              }
            }
          }
          const content = Cesium.VoxelContent.fromMetadataArray([dataTile]);
          return Promise.resolve(content);
        };

        function createPrimitive(provider, customShader) {
          viewer.scene.primitives.removeAll();

          const voxelPrimitive = viewer.scene.primitives.add(
            new Cesium.VoxelPrimitive({
              provider: provider,
              customShader: customShader,
            }),
          );

          viewer.voxelInspector.viewModel.voxelPrimitive = voxelPrimitive;
          viewer.camera.flyToBoundingSphere(voxelPrimitive.boundingSphere, {
            duration: 0.0,
          });

          return voxelPrimitive;
        }

        const customShaderColor = new Cesium.CustomShader({
          fragmentShaderText: `void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
  {
      material.diffuse = fsInput.metadata.color.rgb;
      float transparency = 1.0 - fsInput.metadata.color.a;

      // To mimic light scattering, use exponential decay
      float thickness = fsInput.voxel.travelDistance * 16.0;
      material.alpha = 1.0 - pow(transparency, thickness);
  }`,
        });

        Sandcastle.addToolbarMenu([
          {
            text: "Ellipsoid - Procedural Tile",
            onselect: function () {
              const provider = new ProceduralSingleTileVoxelProvider(
                Cesium.VoxelShapeType.ELLIPSOID,
              );
              provider.minBounds.z = 0.0;
              provider.maxBounds.z = 1000000.0;
              const primitive = createPrimitive(provider, customShaderColor);
            },
          },
          {
            text: "Cylinder - Procedural Tile",
            onselect: function () {
              const provider = new ProceduralSingleTileVoxelProvider(
                Cesium.VoxelShapeType.CYLINDER,
              );
              const primitive = createPrimitive(provider, customShaderColor);
            },
          },
          {
            text: "Box - Procedural Tile",
            onselect: function () {
              const provider = new ProceduralSingleTileVoxelProvider(
                Cesium.VoxelShapeType.BOX,
              );
              const primitive = createPrimitive(provider, customShaderColor);
            },
          },
          {
            text: "Box - Procedural Tileset",
            onselect: function () {
              const provider = new ProceduralMultiTileVoxelProvider(
                Cesium.VoxelShapeType.BOX,
              );
              const primitive = createPrimitive(provider, customShaderColor);
            },
          },
          {
            text: "Ellipsoid - Procedural Tileset",
            onselect: function () {
              const provider = new ProceduralMultiTileVoxelProvider(
                Cesium.VoxelShapeType.ELLIPSOID,
              );
              provider.minBounds.z = 0.0;
              provider.maxBounds.z = 1000000.0;
              const primitive = createPrimitive(provider, customShaderColor);
            },
          },
          {
            text: "Cylinder - Procedural Tileset",
            onselect: function () {
              const provider = new ProceduralMultiTileVoxelProvider(
                Cesium.VoxelShapeType.CYLINDER,
              );
              const primitive = createPrimitive(provider, customShaderColor);
            },
          },
        ]);

        const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
        handler.setInputAction(function (movement) {
          const scene = viewer.scene;
          const camera = scene.camera;
          const mousePosition = movement.position;
          const pickedPrimitive = scene.pick(mousePosition);
          console.log(pickedPrimitive);
        }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
        //Sandcastle_End
      };
      if (typeof Cesium !== "undefined") {
        window.startupCalled = true;
        window.startup(Cesium).catch((error) => {
          "use strict";
          console.error(error);
        });
        Sandcastle.finishedLoading();
      }
    </script>
  </body>
</html>
